import Scene from './Scene.js'
import { CreateProgram } from '../Utils.js';
import Obj3D from './Obj3D.js';
import Geo from './Geo.js';
import Mat from './Mat.js';

const defAttr = () => ({
    //纹理对象
    texture: null,
    texture1: null,
    //深度纹理对象
    textureDepth: null,
    //帧缓冲区对象
    frameBuffer: null,
    //深度缓冲区对象
    depthBuffer: null,

    //最后的渲染队列
    lastChildren: [],
    lastDrawObjs: new Map(),

    //深度元素队列
    depthElemenets: [],
    depthIsUpdate: true,

    width: 1024,
    height: 1024,
});

export default class Frame extends Scene {
    constructor(attr) {
        super(Object.assign(defAttr(), attr));
        this._init();
    }

    _init() {
        const { gl } = this;
        this.texture = gl.createTexture();
        this.texture1 = gl.createTexture();
        this.textureDepth = gl.createTexture();

        this.frameBuffer = gl.createFramebuffer();
        this.depthBuffer = gl.createRenderbuffer();

        //加载深度信息的shader
        this._addDepthProgram();

    }

    //#region lastChildren
    //独立于渲染队列, 用于需要获取texture对象的渲染, 在渲染队列的最后进行渲染
    addLast(element) {
        const { gl, lastChildren, lastDrawObjs } = this;
        lastChildren.push(element);
        element.parent = this;
        element.init(gl);
        this.addDrawObjs(element, lastDrawObjs);

        //递归出所有子类
        let child = this._getChildren(element);
        for (let obj of child) {
            this.addDrawObjs(obj, lastDrawObjs);
        }
    }

    addDrawObjs(element, drawObjsMap) {
        super.addDrawObjs(element, drawObjsMap);
        this.depthIsUpdate = true;
    }

    removeLast(element) {
        const { lastChildren, lastDrawObjs } = this;
        const i = lastChildren.indexOf(element);

        if (i != -1) {
            this.removeDrawObjs(element, lastDrawObjs);
            children.splice(i, 1);

            //递归出所有子类
            let child = this._getChildren(element);
            for (let obj of child) {
                this.removeDrawObjs(obj, lastDrawObjs);
            }
        }
    }

    removeDrawObjs(element, drawObjsMap) {
        super.removeDrawObjs(element, drawObjsMap);
        this.depthIsUpdate = true;
    }
    //#endregion

    update(_texture) {
        const { gl, width, height } = this;
        const textureIndex = 0;
        this._createTextureBuffer(textureIndex, _texture);
        this._createFrameBuffer(textureIndex, _texture);
        this._createDepthBuffer();
        //设置渲染尺寸
        gl.viewport(0, 0, width, height);
    }
    updateOfView() {
        const { gl, width, height } = this;
        //设置渲染尺寸
        gl.viewport(0, 0, width, height);
    }

    reset() {
        const { gl } = this;
        const { canvas: { width, height } } = gl;
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.bindRenderbuffer(gl.RENDERBUFFER, null);
        gl.bindTexture(gl.TEXTURE_2D, null);
        gl.viewport(0, 0, width, height);
    }

    //#region draw
    //首次渲染
    draw([r, g, b, a] = [0, 0, 0, 1]) {

        this.update(this.texture);
        super.draw([r, g, b, a]);
        this.reset();

        return this.texture;
    }
    //二次渲染
    drawLast([r, g, b, a] = [0, 0, 0, 1]) {
        const { gl, programs, lastDrawObjs } = this;

        this.update(this.texture1);
        super.draw([r, g, b, a]);//这个地方应该不需要重新渲染一遍, 我们已经有贴图数据了
        //进行lastMap的渲染
        for (let [key, value] of lastDrawObjs) {
            const { program, attribs, uniforms } = programs.get(key);
            gl.useProgram(program);

            value.forEach(element => {
                element.update(gl, attribs, uniforms);
                element.draw(gl);
            });
        }
        this.reset();
        return this.texture1;
    }
    //渲染深度图
    drawDepth([r, g, b, a] = [0, 0, 0, 1]) {
        const { gl, depthIsUpdate, lastDrawObjs, drawObjs, programs } = this;
        if (depthIsUpdate) {
            this.depthIsUpdate = false;
            this.depthElemenets = [];
            const create = function (ele) {
                return new Obj3D({
                    geo: new Geo({
                        data: { 'a_Position': ele.geo.data['a_Position'], },
                        element: ele.geo.element
                    }),
                    mat: new Mat({
                        program: 'depth',
                        mode: ele.mat.mode,
                        depthTest: ele.mat.depthTest,
                        data: {
                            'u_PvM4': ele.mat.data['u_PvM4'],
                            'u_ModelM4': ele.mat.data['u_ModelM4'],
                        }
                    }),
                });
            }
            for (let [key, val] of drawObjs) {
                for (let ele of val) {
                    const obj = create(ele)
                    obj.init(gl);
                    this.depthElemenets.push(obj);
                }
            }
            for (let [key, val] of lastDrawObjs) {
                for (let ele of val) {
                    const obj = create(ele)
                    obj.init(gl);
                    this.depthElemenets.push(obj);
                }
            }
        }

        const { program, attribs, uniforms } = programs.get('depth');
        gl.useProgram(program);

        this.update(this.textureDepth);
        this.depthElemenets.forEach(element => {
            element.update(gl, attribs, uniforms);
            element.draw(gl);
        });
        this.reset();

        return this.textureDepth;
    }
    //渲染到视图上
    drawView([r, g, b, a] = [0, 0, 0, 1]) {
        this.updateOfView();
        super.draw([r, g, b, a]);
    }
    //#endregion

    //#region setLastAttrib/Uniform
    setLastUniform(name, uniform) {
        const { lastChildren } = this;
        lastChildren.forEach(u => {
            u.setUniform(name, uniform);
        })
    }
    setLastAttrib(name, attrib) {
        const { lastChildren } = this;
        lastChildren.forEach(u => {
            u.setAttrib(name, attrib);
        })
    }
    setLastMap(name, map) {
        const { lastChildren } = this;
        lastChildren.forEach(u => {
            u.setMap(name, map);
        })
    }
    //#endregion

    //#region 帧缓冲区
    //创建纹理贴图 Texture
    _createTextureBuffer(index, _texture) {
        const { gl, width, height } = this;

        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
        gl.activeTexture(gl[`TEXTURE${index}`]);

        gl.bindTexture(gl.TEXTURE_2D, _texture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);//设置滤波器
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    }
    //创建帧缓冲区 FrameBuffer
    _createFrameBuffer(index, _texture) {
        const { gl, texture, frameBuffer } = this;

        gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, _texture, index);
    }
    //创建深度缓冲区 depthBuffer
    _createDepthBuffer() {
        const { gl, width, height, depthBuffer } = this;
        gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
        gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);//配装深度缓冲区
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);//向缓冲区中添加渲染缓冲区
    }
    //#endregion


    //创建深度program
    _addDepthProgram() {
        const { gl } = this;
        const axisVS = `    attribute vec4 a_Position;
        uniform mat4 u_ModelM4;
        uniform mat4 u_PvM4;
        void main(){
            gl_Position = u_PvM4 * u_ModelM4 * a_Position;
        }`;
        const axisFS = `    precision mediump float;
        void main(){
            gl_FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
        }`;
        this.regProgram('depth', {
            program: CreateProgram(gl, axisVS, axisFS),
            attribs: ['a_Position'],
            uniforms: ['u_PvM4', 'u_ModelM4']
        });
        return 'depth';
    }
}